
#include <dlfcn.h>
#include <GL/glx.h>
#include <sys/time.h>
#include "glext.h"

typedef void *(*glXGetProcAddress_fn)(const GLubyte *procname);
typedef GLXFBConfig *(*glXChooseFBConfig_fn)(Display *dpy, int screen, const int *attribList, int *nitems);
typedef XVisualInfo *(*glXGetVisualFromFBConfig_fn)(Display *dpy, GLXFBConfig config);
typedef GLXContext (*glXCreateContext_fn)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct);
typedef Bool (*glXMakeCurrent_fn)(Display *dpy, GLXDrawable drawable, GLXContext ctx);
typedef void (*glXSwapBuffers_fn)(Display *dpy, GLXDrawable drawable);

#define glXGetProcAddress glXGetProcAddress_ptr
#define glXChooseFBConfig glXChooseFBConfig_ptr
#define glXGetVisualFromFBConfig glXGetVisualFromFBConfig_ptr
#define glXCreateContext glXCreateContext_ptr
#define glXMakeCurrent glXMakeCurrent_ptr
#define glXSwapBuffers glXSwapBuffers_ptr

// shader
PFNGLCREATESHADERPROC glCreateShader;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLCOMPILESHADERPROC glCompileShader;
// program
PFNGLCREATEPROGRAMPROC glCreateProgram;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
PFNGLLINKPROGRAMPROC glLinkProgram;
PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLATTACHSHADERPROC glAttachShader;
PFNGLGETPROGRAMIVPROC glGetProgramiv;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
PFNGLUNIFORM1FPROC glUniform1f;
PFNGLUNIFORM2FPROC glUniform2f;
// vertex array
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
// buffer
PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBUFFERDATAPROC glBufferData;
// draw call
PFNGLDRAWARRAYSINSTANCEDPROC glDrawArraysInstanced;

void *load_opengl_command(const char *cmd) {
    void *r = SDL_GL_GetProcAddress(cmd);
    if (r == NULL) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to load %s command.", cmd);
    }

    return r;
}

void load_opengl_commands() {
    // shader
    glCreateShader = reinterpret_cast<PFNGLCREATESHADERPROC>(load_opengl_command("glCreateShader"));
    glShaderSource = reinterpret_cast<PFNGLSHADERSOURCEPROC>(load_opengl_command("glShaderSource"));
    glCompileShader = reinterpret_cast<PFNGLCOMPILESHADERPROC>(load_opengl_command("glCompileShader"));
    // program
    glCreateProgram = reinterpret_cast<PFNGLCREATEPROGRAMPROC>(load_opengl_command("glCreateProgram"));
    glLinkProgram = reinterpret_cast<PFNGLLINKPROGRAMPROC>(load_opengl_command("glLinkProgram"));
    glUseProgram = reinterpret_cast<PFNGLUSEPROGRAMPROC>(load_opengl_command("glUseProgram"));
    glAttachShader = reinterpret_cast<PFNGLATTACHSHADERPROC>(load_opengl_command("glAttachShader"));
    glGetProgramiv = reinterpret_cast<PFNGLGETPROGRAMIVPROC>(load_opengl_command("glGetProgramiv"));
    glGetProgramInfoLog = reinterpret_cast<PFNGLGETPROGRAMINFOLOGPROC>(load_opengl_command("glGetProgramInfoLog"));
    glGetUniformLocation = reinterpret_cast<PFNGLGETUNIFORMLOCATIONPROC>(load_opengl_command("glGetUniformLocation"));
    glUniform1f = reinterpret_cast<PFNGLUNIFORM1FPROC>(load_opengl_command("glUniform1f"));
    glUniform2f = reinterpret_cast<PFNGLUNIFORM2FPROC>(load_opengl_command("glUniform2f"));
    // vertex array
    glGenVertexArrays = reinterpret_cast<PFNGLGENVERTEXARRAYSPROC>(load_opengl_command("glGenVertexArrays"));
    glBindVertexArray = reinterpret_cast<PFNGLBINDVERTEXARRAYPROC>(load_opengl_command("glBindVertexArray"));
    glVertexAttribPointer = reinterpret_cast<PFNGLVERTEXATTRIBPOINTERPROC>(load_opengl_command("glVertexAttribPointer"));
    glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC) load_opengl_command("glEnableVertexAttribArray");
    // buffer
    glGenBuffers = (PFNGLGENBUFFERSPROC) load_opengl_command("glGenBuffers");
    glBindBuffer = (PFNGLBINDBUFFERPROC) load_opengl_command("glBindBuffer");
    glBufferData = (PFNGLBUFFERDATAPROC) load_opengl_command("glBufferData");
    // draw call
    glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC) load_opengl_command("glDrawArraysInstanced");
}

int main(void) {
    glXGetProcAddress_fn glXGetProcAddress_ptr;
    glXChooseFBConfig_fn glXChooseFBConfig_ptr;
    glXGetVisualFromFBConfig_fn glXGetVisualFromFBConfig_ptr;
    glXCreateContext_fn glXCreateContext_ptr;
    glXMakeCurrent_fn glXMakeCurrent_ptr;
    glXSwapBuffers_fn glXSwapBuffers_ptr;
    void *libgl;
    GLXFBConfig *fbc;
    int fbcount;
    int attrib[] = {
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
        GLX_DOUBLEBUFFER, True,
        GLX_RED_SIZE, 8,
        GLX_GREEN_SIZE, 8,
        GLX_BLUE_SIZE, 8,
        GLX_DEPTH_SIZE, 24,
        GLX_STENCIL_SIZE, 8,
        None
    };
    XVisualInfo *visinfo;
    XSetWindowAttributes attr;
    Display *disp;
    Window win;
    GLXContext ctx;

    libgl = dlopen("libGL.so.1", RTLD_LAZY);
    glXGetProcAddress = dlsym(libgl, "glXGetProcAddress");
    glXChooseFBConfig = (glXChooseFBConfig_fn) glXGetProcAddress((const GLubyte *) "glXChooseFBConfig");
    glXGetVisualFromFBConfig = (glXGetVisualFromFBConfig_fn) glXGetProcAddress((const GLubyte *) "glXGetVisualFromFBConfig");
    glXCreateContext = (glXCreateContext_fn) glXGetProcAddress((const GLubyte *) "glXCreateContext");
    glXMakeCurrent = (glXMakeCurrent_fn) glXGetProcAddress((const GLubyte *) "glXMakeCurrent");
    glXSwapBuffers = (glXSwapBuffers_fn) glXGetProcAddress((const GLubyte *) "glXSwapBuffers");

    disp = XOpenDisplay(NULL);
    fbc = glXChooseFBConfig(disp, DefaultScreen(disp), attrib, &fbcount);
    visinfo = glXGetVisualFromFBConfig(disp, fbc[0]);

    attr.background_pixel = 0;
    attr.border_pixel = 0;
    attr.colormap = XCreateColormap(disp, RootWindow(disp, DefaultScreen(disp)), visinfo->visual, AllocNone);
    attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
    win = XCreateWindow(disp, RootWindow(disp, DefaultScreen(disp)),
            0, 0, 600, 600,
            0, visinfo->depth, InputOutput, visinfo->visual,
            CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &attr);
    XMapWindow(disp, win);

    ctx = glXCreateContext(disp, visinfo, NULL, True);
    glXMakeCurrent(disp, win, ctx);

    load_opengl_commands();

    while (1) {
        if (XPending(disp)) {
            XEvent evt;
            XNextEvent(disp, &evt);

            switch (evt.type) {
            case KeyPress:
                return 0;
            }
        } else {
            struct timeval tp;
            float time;

            gettimeofday(&tp, NULL);
            time = tp.tv_sec + tp.tv_usec / 1e6;
            glXSwapBuffers(disp, win);
        }
    }
}
